#include "StdAfx.h"
#include "DirectoryWatch.h"
#include "yasli/Errors.h"

#ifdef WIN32
# define _WIN32_WINNT 0x0500
# include <windows.h>

class DirectoryWatchImpl{
public:
	DirectoryWatchImpl(DirectoryWatch* owner)
	: owner_(owner)
	, handle_(INVALID_HANDLE_VALUE)
	, recurse_(false)
	{
		ZeroMemory((void*)&overlapped_, sizeof(overlapped_));
	}

	bool install(const char* path, bool recurse)
	{
		//wchar_t buffer[MAX_PATH];
		//if(!MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, path, -1, buffer, sizeof(buffer)))
		//	return false;

		owner_->path_ = path;
		recurse_ = recurse;

		DWORD attributes = GetFileAttributes(path);

		if(attributes == INVALID_FILE_ATTRIBUTES)
			return false;
		if((attributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
			return false;

		handle_ = CreateFile(path, FILE_LIST_DIRECTORY,
			FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0,
							 OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, 0);

		if(handle_ == INVALID_HANDLE_VALUE)
			return false;

		overlapped_.hEvent = CreateEvent(0, FALSE, FALSE, "VimKickDirChange");;
		if(overlapped_.hEvent == INVALID_HANDLE_VALUE)
			return false;

		readChanges();
		return true;
	}

	void readChanges()
	{
		ReadDirectoryChangesW(handle_, buffer_, sizeof(buffer_), recurse_ ? TRUE : FALSE, 
							  FILE_NOTIFY_CHANGE_FILE_NAME |
							  FILE_NOTIFY_CHANGE_DIR_NAME | 
							  FILE_NOTIFY_CHANGE_SIZE |
							  FILE_NOTIFY_CHANGE_LAST_WRITE,
							  &bytesReturned_, &overlapped_, 0);
	}


	void uninstall()
	{
		if(handle_ != INVALID_HANDLE_VALUE){
			CloseHandle(handle_);
			handle_ = INVALID_HANDLE_VALUE;
		}
	}

	bool poll()
	{
		if(handle_ == INVALID_HANDLE_VALUE)
			return false;
		if(!GetOverlappedResult(handle_, &overlapped_, &bytesReturned_, FALSE))
			return false;
        ResetEvent(overlapped_.hEvent);
		ASSERT(owner_->listener_ != 0);

		int offset = 0;


		char fileName[MAX_PATH * 2];
		char oldRenameFileName[MAX_PATH * 2];

		FILE_NOTIFY_INFORMATION* notifyInfo = 0;
		do{
			notifyInfo = (FILE_NOTIFY_INFORMATION*)&buffer_[offset];
//#ifdef UNICODE
//			lstrcpynW(sfileName, notifyInfo->FileName,
//					  min(MAX_PATH, notifyInfo->FileNameLength / sizeof(wchar_t) + 1));
//#else
			int count = WideCharToMultiByte(CP_UTF8, 0, notifyInfo->FileName,
											notifyInfo->FileNameLength / sizeof(wchar_t),
											fileName, MAX_PATH - 1, NULL, NULL);
			fileName[count] = '\0';

			switch(notifyInfo->Action){
			case FILE_ACTION_ADDED:
				owner_->listener_->onFileAdded(owner_, fileName);
				break;
			case FILE_ACTION_REMOVED:
				owner_->listener_->onFileRemoved(owner_, fileName);
				break;
			case FILE_ACTION_MODIFIED:
				owner_->listener_->onFileModified(owner_, fileName);
				break;
			case FILE_ACTION_RENAMED_OLD_NAME:
				strcpy(oldRenameFileName, fileName);
				break;
			case FILE_ACTION_RENAMED_NEW_NAME:
				owner_->listener_->onFileRenamed(owner_, oldRenameFileName, fileName);
				strcpy(oldRenameFileName, "");
				break;
			}

			offset += notifyInfo->NextEntryOffset;
		} while(notifyInfo->NextEntryOffset != 0);
		
		readChanges();
		return true;
	}
protected:
	DirectoryWatch* owner_;
	HANDLE handle_;
	OVERLAPPED overlapped_;
	unsigned long bytesReturned_;
	char buffer_[1024 * 24];
	bool recurse_;
};
#else

class DirectoryWatchImpl{
public:
	DirectoryWatchImpl(DirectoryWatch* owner)
	: owner_(owner)
	, recurse_(false)
	{
	}

	bool install(const char* path, bool recurse)
	{
	}

	void readChanges()
	{
	}


	void uninstall()
	{
	}

	bool poll()
	{
		return false;
	}
protected:
	DirectoryWatch* owner_;
	bool recurse_;
};

#endif

#pragma warning(push)
#pragma warning(disable : 4355) // 'this' : used in base member initializer list
DirectoryWatch::DirectoryWatch(DirectoryListener* listener)
: impl_(new DirectoryWatchImpl(this))
, listener_(0)
{
	if(listener)
		setListener(listener);
}
#pragma warning(pop)

DirectoryWatch::~DirectoryWatch()
{
	delete impl_;
}

void DirectoryWatch::setListener(DirectoryListener* listener)
{
	ASSERT(listener != 0);
	listener_ = listener;
}

bool DirectoryWatch::install(const char* path, bool recurse)
{
	return impl_->install(path, recurse);
}

void DirectoryWatch::uninstall()
{
	impl_->uninstall();
}

bool DirectoryWatch::poll()
{
	return impl_->poll();
}
